package joelwilson.lucene.dao;

import static joelwilson.lucene.SystemConstants.creationTimeMapping;
import static joelwilson.lucene.SystemConstants.updateTimeMapping;

import java.util.Date;
import java.util.Map;
import java.util.Set;

import joelwilson.lucene.SystemConstants;
import joelwilson.lucene.dto.Mapping;

import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Index;
import org.apache.lucene.document.Field.Store;

import com.google.appengine.api.blobstore.BlobKey;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Text;

public class DAOUtils {
	
	/**
	 * Will populate the entity with values from the data based on rules in the supplied mappings.
	 * 
	 * If timestamps is true "lastupdated" will update set based on the current time, and if not supplied in either
	 * the entity or data, "created" will be set based on the same time as "lastupdated".
	 * 
	 * Mappings need only be provided for special case properties where either the data should not be index or stored at all,
	 * or the object is String representation of Key or BlobKey and it should be converted to the proper Type.
	 * 
	 * Default for non-specified and/or non-timestamp values is to be stored and indexed.
	 * 
	 * @param entity
	 * @param data
	 * @param timestamps
	 * @param mappings
	 */
	public static void populateEntity(Entity entity, Map<String, Object> data, boolean timestamps, Mapping... mappings) {
		if (entity == null)
			throw new IllegalArgumentException("Entity object passed in must be valid object.");
		
		// go over the keys in the data map
		if (data != null) {
			Set<String> keys = data.keySet();
			for (String key : keys) {
				
				// control boolean to use later if there is no mapping for this property
				boolean foundMapping = false;
				
				// loop through the mappings to see if present
				for (Mapping mapping: mappings) {
					if (key.equals(mapping.getMapping())) {
						// mark the we found a mapping for this key
						foundMapping = true;
						
						// store based on the rules in the mapping
						if (mapping.isDatastoreStored()) {
							// get the object we are storing
							Object storedValue = data.get(key);
							
							// check if it needs special conversion
							if (mapping.getType() != Mapping.Type.Property && storedValue instanceof String) {
								switch (mapping.getType()) {
								case Key:
									storedValue = KeyFactory.stringToKey((String)storedValue);
									break;
								case BlobKey:
									storedValue = new BlobKey((String)storedValue);
									break;
								}
							}
							
							// deal with long text properties
							if (storedValue instanceof String && ((String)storedValue).length() > 500)
								storedValue = new Text((String)storedValue);
							
							// store the object in the entity
							if (mapping.isDatastoreIndexed())
								entity.setProperty(key, storedValue);
							else
								entity.setUnindexedProperty(key, storedValue);
						}
						
						break;
					}
				}
				
				// store the property if no mapping rules present
				if (!foundMapping)
					entity.setProperty(key, data.get(key));
			}
		}
		
		// set the timestamps if the boolean is set
		if (timestamps) {
			Date timestamp = new Date();
			// set the update time
			entity.setProperty(updateTimeMapping.getMapping(), timestamp);
			
			// set the creation time if not already present
			if (!entity.hasProperty(creationTimeMapping.getMapping()))
				entity.setProperty(creationTimeMapping.getMapping(), timestamp);
		}
	}

	/**
	 * Will populate the entity with values from the data based on rules in the supplied mappings.
	 * 
	 * Mappings need only be provided for special case properties where either the data should not be Analyzed or should be stored.
	 * 
	 * Default for non-specified values is to be Analyzed and NOT stored.
	 * 
	 * @param entity
	 * @param data
	 * @param mappings
	 */
	public static void populateDocument(Document document, Entity entity, Mapping... mappings) {
		populateDocument(document, entity.getProperties(), mappings);
		
		// populate the entity key itself
		if (document.getField(SystemConstants.keyMapping.getMapping()) == null) {
			document.add(new Field(SystemConstants.keyMapping.getMapping(), KeyFactory.keyToString(entity.getKey()), SystemConstants.keyMapping.getLuceneStoreMode(), SystemConstants.keyMapping.getLuceneIndexMode()));
		}
	}
	
	/**
	 * Will populate the entity with values from the data based on rules in the supplied mappings.
	 * 
	 * Mappings need only be provided for special case properties where either the data should not be Analyzed or should be stored.
	 * 
	 * Default for non-specified values is to be Analyzed and NOT stored.
	 * 
	 * @param entity
	 * @param data
	 * @param mappings
	 */
	public static void populateDocument(Document document, Map<String, Object> data, Mapping...mappings) {
		if (document == null || data == null)
			throw new IllegalArgumentException("Document and Map must be valid objects of their respective types.");
		
		// go over the keys in the data map
		Set<String> keys = data.keySet();
		for (String key : keys) {
			
			// control boolean to use later if there is no mapping for this property
			boolean foundMapping = false;
			
			// loop through the mappings to see if present
			for (Mapping mapping: mappings) {
				if (key.equals(mapping.getMapping())) {
					// mark the we found a mapping for this key
					foundMapping = true;
					
					// store based on the rules in the mapping
					if (mapping.isLuceneStored() || mapping.isLuceneIndexed()) {
						// get the object we are storing
						String storedValue = data.get(key) instanceof String ? (String)data.get(key) : data.get(key).toString();
						
						// store the object in the entity
						document.add(new Field(mapping.getMapping(), storedValue, mapping.getLuceneStoreMode(), mapping.getLuceneIndexMode()));
					}
					
					break;
				}
			}
			
			// store the property if no mapping rules present
			if (!foundMapping) {
				String storedValue = data.get(key) instanceof String ? (String)data.get(key) : data.get(key).toString();
				document.add(new Field(key, storedValue, Store.NO, Index.ANALYZED));
			}
		}
	}
}
